package com.sneaker.shower.socket;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sneaker.shower.socket.common.PSWsRequest;
import com.sneaker.shower.socket.common.PSWsResponse;
import com.sneaker.shower.socket.feature.AbstractChannel;
import com.sneaker.shower.socket.feature.SignAble;
import org.apache.tomcat.util.buf.Utf8Encoder;
import org.apache.tomcat.websocket.Constants;
import org.apache.tomcat.websocket.WsRemoteEndpointBase;
import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.AbstractWebSocketSession;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * <h1>StandarChannel</h1>
 * <pre>
 * @Description
 * </pre>
 *
 * @Author lopo
 * @Date 2018/10/19
 */
public class StandarChannel extends AbstractChannel {
    private static final Long ACT_TIME_OUT = 5 * 1000L;
    private static final Integer INVALID = 5;
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final PSWsRequest request = new PSWsRequest();
    protected static ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(6);
    private static Logger logger = LoggerFactory.getLogger(StandarChannel.class);
    private static Auther auther;
    private static Field field;
    private static Method ss;

    static {
        try {
            Field f = WsRemoteEndpointBase.class.getDeclaredField("base");
            f.setAccessible(true);
            field = f;
            ss = dd();
            request.setContent("认证未通过请重新登录");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    private final ByteBuffer headerBuffer = ByteBuffer.allocate(14);
    private final ByteBuffer outputBuffer = ByteBuffer.allocate(Integer.getInteger(
            "org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE", 8 * 1024)
            .intValue());
    private final CharsetEncoder encoder = new Utf8Encoder();
    private final ByteBuffer encoderBuffer = ByteBuffer.allocate(Integer.getInteger(
            "org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE", 8 * 1024)
            .intValue());
    private transient AbstractWebSocketSession webSocketSession;
    private List<Group<Channel>> groups = new ArrayList<>();
    private Long createTime;
    private volatile Long lastActTime;
    private volatile Integer invalid;

    public StandarChannel(SignAble signAble, WebSocketSession webSocketSession) {

        super(signAble);
        createTime = System.currentTimeMillis();
        lastActTime = System.currentTimeMillis();
        invalid = 0;
        this.webSocketSession = (AbstractWebSocketSession) webSocketSession;
    }

    private static Method dd() {
        Method[] ss = WsRemoteEndpointImplBase.class.getDeclaredMethods();
        for (Method m : ss) {
            if (m.getName().equals("sendMessageBlock") && m.getParameterTypes()[0].getName().equals("byte") && m.getParameterTypes().length == 3) {
                m.setAccessible(true);
                return m;
            }
        }
        return null;
    }

    public static void setAuther(Auther auther) {
        StandarChannel.auther = auther;
    }

    @Override
    public void check() {
        Long now = System.currentTimeMillis();
        if (now - lastActTime > ACT_TIME_OUT) {
            ++invalid;
        } else {
            invalid = 0;
        }
        if (invalid > INVALID) {
//            invalid();
        }
        if (invalid > (INVALID + 1)) {
//            invalid();
        }
        lastActTime = now;
        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    webSocketSession.sendMessage(new PingMessage());
                } catch (Exception e) {
                    broken();
                }
            }
        }, 10 * 1000, TimeUnit.MILLISECONDS);
    }

    @Override
    public void valid() {
        super.valid();
        check();
    }

    @Override
    public void send(Serializable s) {
        try {
            TextMessage textMessage = null;
            if (s instanceof CharSequence) {
                textMessage = new TextMessage((CharSequence) s);
            } else if (s instanceof Number) {
                textMessage = new TextMessage(s.toString());
            } else {
                textMessage = new TextMessage(objectMapper.writeValueAsString(s));
            }
            logger.info("message send {}", textMessage);
            RemoteEndpoint.Basic basic = ((Session) webSocketSession.getNativeSession()).getBasicRemote();
            WsRemoteEndpointImplBase base = (WsRemoteEndpointImplBase) field.get(basic);
            CharBuffer charBuffer = CharBuffer.wrap(textMessage.getPayload());
            synchronized (base) {
                boolean isDone = false;
                while (!isDone) {
                    encoderBuffer.clear();
                    CoderResult cr = encoder.encode(charBuffer, encoderBuffer, true);
                    if (cr.isError()) {
                        throw new IllegalArgumentException(cr.toString());
                    }
                    isDone = !cr.isOverflow();
                    encoderBuffer.flip();
                    ss.invoke(base, Constants.OPCODE_TEXT, encoderBuffer, true && isDone);
                }
            }
//            webSocketSession.sendMessage(textMessage);
        } catch (Exception e) {
            logger.error("send fail ", e);
            this.broken();
            throw new RuntimeException(e);
        }
    }

    @Override
    public Collection<Group<Channel>> groups() {
        return groups;
    }

    @Override
    public void doAuth(PSWsRequest request, PSWsResponse response) {
        if (!auther.doAuth(request)) {
            logger.info("{} auth fail with {}", this.sign(), request);
            send(StandarChannel.request);
            broken();
            return;
        }
        valid();
    }

    @Override
    public synchronized void broken() {
        logger.info("{} channel broken", this.sign());
        super.broken();
        try {
            this.webSocketSession.close();
        } catch (IOException e) {
            logger.error("send fail ", e);
        }
    }

    @Override
    public Serializable getId() {
        return webSocketSession.getId();
    }
}
